<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>粒子形状变形动画</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            color: #fff;
        }
        #scene-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        .ui-overlay {
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 10;
            text-align: center;
        }
        .info-box, .control-box {
            background-color: rgba(0, 0, 50, 0.5);
            border: 1px solid #4a90e2;
            padding: 8px 15px;
            border-radius: 8px;
            box-shadow: 0 0 15px rgba(74, 144, 226, 0.5);
            font-size: 16px;
        }
        #shape-info {
             margin-bottom: 20px;
        }
        .control-panel {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 10;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 15px;
        }
        #change-shape-btn {
            background-color: #4a90e2;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s ease;
        }
        #change-shape-btn:hover {
            background-color: #357ABD;
        }
        #change-shape-btn:disabled {
            background-color: #555;
            cursor: not-allowed;
        }
        .color-palette {
            display: flex;
            gap: 10px;
        }
        .color-swatch {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            cursor: pointer;
            border: 2px solid #fff;
            transition: transform 0.2s ease;
        }
        .color-swatch:hover {
            transform: scale(1.1);
        }
    </style>
</head>
<body>

    <div id="scene-container"></div>

    <div class="ui-overlay">
        <div id="shape-info" class="info-box">形状: 球体 (点击改变形状)</div>
    </div>

    <div class="control-panel">
        <div class="control-box">
            <button id="change-shape-btn">改变形状</button>
            <div class="color-palette">
                <div class="color-swatch" style="background: linear-gradient(45deg, #FFD700, #FFA500);" data-color1="#FFD700" data-color2="#FFA500"></div>
                <div class="color-swatch" style="background: linear-gradient(45deg, #8A2BE2, #4169E1);" data-color1="#8A2BE2" data-color2="#4169E1"></div>
                <div class="color-swatch" style="background: linear-gradient(45deg, #32CD32, #00FFFF);" data-color1="#32CD32" data-color2="#00FFFF"></div>
                <div class="color-swatch" style="background: linear-gradient(45deg, #FF6347, #FFD700, #32CD32, #1E90FF);" data-color1="rainbow" data-color2=""></div>
            </div>
        </div>
    </div>

    <!-- 导入所需库 -->
    <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
    <script type="importmap">
    {
        "imports": {
            "three": "https://unpkg.com/three@0.157.0/build/three.module.js",
            "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
        }
    }
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        // --- 基本设置 ---
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 100;

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.getElementById('scene-container').appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        
        // --- UI元素 ---
        const shapeInfo = document.getElementById('shape-info');
        const changeShapeBtn = document.getElementById('change-shape-btn');
        const colorSwatches = document.querySelectorAll('.color-swatch');

        // --- 粒子系统 ---
        const PARTICLE_COUNT = 20000;
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(PARTICLE_COUNT * 3);
        const colors = new Float32Array(PARTICLE_COUNT * 3);
        const sizes = new Float32Array(PARTICLE_COUNT);

        let particleSystem;
        let currentShape = 0;
        let isMorphing = false;
        
        const shapes = [
            { name: '球体', generator: createSphere },
            { name: '立方体', generator: createCube },
            { name: '金字塔', generator: createPyramid },
            { name: '圆环', generator: createTorus },
            { name: '星系', generator: createGalaxy },
            { name: '波浪', generator: createWave },
        ];
        
        // --- 形状顶点生成函数 ---
        
        function createSphere() {
            const points = [];
            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const phi = Math.acos(-1 + (2 * i) / PARTICLE_COUNT);
                const theta = Math.sqrt(PARTICLE_COUNT * Math.PI) * phi;
                const x = 50 * Math.cos(theta) * Math.sin(phi);
                const y = 50 * Math.sin(theta) * Math.sin(phi);
                const z = 50 * Math.cos(phi);
                points.push(new THREE.Vector3(x, y, z));
            }
            return points;
        }

        function createCube() {
            const points = [];
            const size = 80;
            const halfSize = size / 2;
            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const x = Math.random() * size - halfSize;
                const y = Math.random() * size - halfSize;
                const z = Math.random() * size - halfSize;
                const face = Math.floor(Math.random() * 6);
                switch(face) {
                    case 0: points.push(new THREE.Vector3(halfSize, y, z)); break;
                    case 1: points.push(new THREE.Vector3(-halfSize, y, z)); break;
                    case 2: points.push(new THREE.Vector3(x, halfSize, z)); break;
                    case 3: points.push(new THREE.Vector3(x, -halfSize, z)); break;
                    case 4: points.push(new THREE.Vector3(x, y, halfSize)); break;
                    case 5: points.push(new THREE.Vector3(x, y, -halfSize)); break;
                }
            }
            return points;
        }

        function createPyramid() {
            const points = [];
            const height = 70;
            const baseSize = 80;
            const halfBase = baseSize / 2;
            // 4 triangular faces + 1 base face = 5 total surfaces
            const particlesPerFace = PARTICLE_COUNT / 5;
            
            // Base
            for (let i = 0; i < particlesPerFace; i++) {
                const x = Math.random() * baseSize - halfBase;
                const z = Math.random() * baseSize - halfBase;
                points.push(new THREE.Vector3(x, -height / 2, z));
            }

            // 4 triangular faces
            for (let i = 0; i < PARTICLE_COUNT - particlesPerFace; i++) {
                const r1 = Math.random();
                const r2 = Math.random();
                const u = 1 - Math.sqrt(r1);
                const v = Math.sqrt(r1) * (1 - r2);
                const w = Math.sqrt(r1) * r2;
                
                const apex = new THREE.Vector3(0, height / 2, 0);
                let p1, p2;

                const face = Math.floor(i / (particlesPerFace * 4 / 4));
                switch(face) {
                    case 0: p1 = new THREE.Vector3(-halfBase, -height/2, -halfBase); p2 = new THREE.Vector3(halfBase, -height/2, -halfBase); break;
                    case 1: p1 = new THREE.Vector3(halfBase, -height/2, -halfBase); p2 = new THREE.Vector3(halfBase, -height/2, halfBase); break;
                    case 2: p1 = new THREE.Vector3(halfBase, -height/2, halfBase); p2 = new THREE.Vector3(-halfBase, -height/2, halfBase); break;
                    case 3: p1 = new THREE.Vector3(-halfBase, -height/2, halfBase); p2 = new THREE.Vector3(-halfBase, -height/2, -halfBase); break;
                }
                const pos = new THREE.Vector3();
                pos.addScaledVector(apex, u).addScaledVector(p1, v).addScaledVector(p2, w);
                points.push(pos);
            }
            return points;
        }
        
        function createTorus() {
            const points = [];
            const R = 40; // Major radius
            const r = 20; // Minor radius
            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const u = Math.random() * 2 * Math.PI;
                const v = Math.random() * 2 * Math.PI;
                const x = (R + r * Math.cos(v)) * Math.cos(u);
                const y = (R + r * Math.cos(v)) * Math.sin(u);
                const z = r * Math.sin(v);
                points.push(new THREE.Vector3(x, y, z));
            }
            return points;
        }

        function createGalaxy() {
            const points = [];
            const arms = 3;
            const armSpread = 2 * Math.PI / arms;
            const radius = 80;
            const spread = 8;
            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const r = Math.random() * radius;
                const armIndex = i % arms;
                const spin = r * 0.1;
                const angle = armIndex * armSpread + spin;
                
                const randomX = (Math.random() - 0.5) * spread * (r / radius);
                const randomY = (Math.random() - 0.5) * spread * (r / radius) * 0.2; // flatter galaxy
                const randomZ = (Math.random() - 0.5) * spread * (r / radius);

                const x = Math.cos(angle) * r + randomX;
                const y = randomY;
                const z = Math.sin(angle) * r + randomZ;
                points.push(new THREE.Vector3(x, y, z));
            }
            return points;
        }
        
        function createWave() {
            const points = [];
            const size = 100;
            const halfSize = size / 2;
            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const x = Math.random() * size - halfSize;
                const z = Math.random() * size - halfSize;
                const dist = Math.sqrt(x*x + z*z);
                const y = Math.sin(dist * 0.3) * 10;
                points.push(new THREE.Vector3(x, y, z));
            }
            return points;
        }


        // --- 初始化粒子系统 ---
        function init() {
            const initialPositions = shapes[0].generator();
            const initialColor1 = new THREE.Color("#FFD700");
            const initialColor2 = new THREE.Color("#FFA500");

            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const i3 = i * 3;
                positions[i3] = initialPositions[i].x;
                positions[i3 + 1] = initialPositions[i].y;
                positions[i3 + 2] = initialPositions[i].z;

                const mixedColor = initialColor1.clone().lerp(initialColor2, Math.random());
                colors[i3] = mixedColor.r;
                colors[i3 + 1] = mixedColor.g;
                colors[i3 + 2] = mixedColor.b;

                sizes[i] = Math.random() * 1.5 + 0.5;
            }

            geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
            geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
            
            const material = new THREE.PointsMaterial({
                size: 1,
                vertexColors: true,
                sizeAttenuation: true,
                depthWrite: false,
                blending: THREE.AdditiveBlending
            });
            
            particleSystem = new THREE.Points(geometry, material);
            scene.add(particleSystem);
        }

        // --- 变形动画函数 ---
        function morphToShape(shapeIndex) {
            if (isMorphing) return;
            isMorphing = true;
            changeShapeBtn.disabled = true;
            shapeInfo.textContent = '变形中...';

            const targetPositions = shapes[shapeIndex].generator();
            const positionAttribute = geometry.attributes.position;

            const tl = gsap.timeline({
                onComplete: () => {
                    isMorphing = false;
                    changeShapeBtn.disabled = false;
                    shapeInfo.textContent = `形状: ${shapes[shapeIndex].name} (点击改变形状)`;
                }
            });

            for (let i = 0; i < PARTICLE_COUNT; i++) {
                const i3 = i * 3;
                const target = targetPositions[i];
                
                tl.to(positionAttribute.array, {
                    [i3]: target.x,
                    [i3+1]: target.y,
                    [i3+2]: target.z,
                    duration: 2,
                    ease: 'power3.inOut'
                }, 0); // Start all animations at the same time
            }

            // Tell Three.js to update the geometry
            gsap.to({}, { duration: 2, onUpdate: () => { positionAttribute.needsUpdate = true; } });
        }

        // --- 颜色变化函数 ---
        function changeColor(color1, color2) {
            const colorAttribute = geometry.attributes.color;
            const c1 = new THREE.Color(color1);
            const isRainbow = color1 === 'rainbow';

            for(let i=0; i < PARTICLE_COUNT; i++) {
                const i3 = i * 3;
                let targetColor;

                if (isRainbow) {
                    targetColor = new THREE.Color().setHSL(Math.random(), 0.8, 0.6);
                } else {
                    const c2 = new THREE.Color(color2);
                    targetColor = c1.clone().lerp(c2, Math.random());
                }

                gsap.to(colorAttribute.array, {
                    [i3]: targetColor.r,
                    [i3 + 1]: targetColor.g,
                    [i3 + 2]: targetColor.b,
                    duration: 1,
                    ease: 'power2.inOut'
                });
            }
             gsap.to({}, { duration: 1, onUpdate: () => { colorAttribute.needsUpdate = true; } });
        }

        // --- 事件监听 ---
        changeShapeBtn.addEventListener('click', () => {
            currentShape = (currentShape + 1) % shapes.length;
            morphToShape(currentShape);
        });

        colorSwatches.forEach(swatch => {
            swatch.addEventListener('click', () => {
                const color1 = swatch.dataset.color1;
                const color2 = swatch.dataset.color2;
                changeColor(color1, color2);
            });
        });

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // --- 动画循环 ---
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            particleSystem.rotation.y += 0.0005;
            renderer.render(scene, camera);
        }

        // --- 启动 ---
        init();
        animate();
    </script>
</body>
</html>